﻿using System;
using System.Linq;
using System.Security.Principal;
using System.Web;
using System.Web.Mvc;

namespace AzManContrib.MVC
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
    public class AuthorizeOperationAttribute : FilterAttribute, IAuthorizationFilter
    {
        private string _operations;
        private string[] _operationsSplit = new string[0];

        public string Operations
        {
            get
            {
                return _operations ?? string.Empty;
            }
            set
            {
                _operations = value;
                _operationsSplit = SplitString(value);
            }
        }

        /// <summary>
        /// Called when authorization is required.
        /// </summary>
        /// <param name="filterContext">The filter context.</param>
        public void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null)
                throw new ArgumentNullException("filterContext");
            if (OutputCacheAttribute.IsChildActionCacheActive(filterContext))
                throw new InvalidOperationException("Cannot use AuthorizeOperationAttribute within child action cache");
            if (AuthorizeCore(filterContext.HttpContext))
            {
                HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache;
                cache.SetProxyMaxAge(new TimeSpan(0L));
                cache.AddValidationCallback(CacheValidateHandler, null);
            }
            else
                HandleUnauthorizedRequest(filterContext);
        }

        protected virtual bool AuthorizeCore(HttpContextBase httpContext)
        {
            if (httpContext == null)
                throw new ArgumentNullException("httpContext");

            IPrincipal user = httpContext.User;

            try
            {
                return user.Identity.IsAuthenticated && _operationsSplit.Length > 0 && _operationsSplit.Any(user.Can);
            }
            catch(Exception)
            {
                return false;
            }
        }

        protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            filterContext.Result = new HttpUnauthorizedResult();
        }

        private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
        {
            validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
        }

        protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext)
        {
            if (httpContext == null)
                throw new ArgumentNullException("httpContext");
            return !AuthorizeCore(httpContext) ? HttpValidationStatus.IgnoreThisRequest : HttpValidationStatus.Valid;
        }

        internal static string[] SplitString(string original)
        {
            if (string.IsNullOrEmpty(original))
                return new string[0];
            return original.Split(new[] {','})
                .Select(piece => new
                                     {
                                         piece,
                                         trimmed = piece.Trim()
                                     }
                )
                .Where(param0 => !string.IsNullOrEmpty(param0.trimmed))
                .Select(param0 => param0.trimmed)
                .ToArray();
        }
    }
}
